import unittest, time, logging, random, Queue, socket, threading
from tinynl import *

class TestDecimalEncodeDecode(unittest.TestCase):
  def test_decimal_encode(self):
    self.assertEqual(decimal_hdr_encode('Blaat'), '5:Blaat')
    self.assertEqual(decimal_hdr_encode('Bla:at'), '6:Bla:at')
    self.assertRaises(TypeError, decimal_hdr_encode, 6)

  def test_decimal_decode(self):
    self.assertEqual(decimal_hdr_decode('5:Blaat'), ('Blaat', ''))
    self.assertEqual(decimal_hdr_decode('5:Blaatbeh'), ('Blaat', 'beh'))
    self.assertEqual(decimal_hdr_decode('6:Bla:at'), ('Bla:at', ''))
    self.assertEqual(decimal_hdr_decode(''), (None, ''))

  def test_decimal_decode_malformed(self):
    self.assertRaises(DecodeError, decimal_hdr_decode, 'Blaat')
    self.assertRaises(DecodeError, decimal_hdr_decode, '1' * 11)

class MockSock:
  def __init__(self, recv='', sleep=.1):
    self.sent = Queue.Queue()
    self.recv_ = recv
    self.sleep_ = sleep

  def sleep(self):
    if self.sleep != 0:
      time.sleep(self.sleep_)

  def sendall(self, s):
    self.sent.put(s)
    self.sleep()

  def gettimeout(self):
    return None

  def recv(self, buffsize):
    if len(self.recv_) > 0:
      n = random.randint(1, min(10, buffsize))
      result, self.recv_ = self.recv_[:n], self.recv_[n:]
      return result
    else:
      time.sleep(10)
      return ''

class TestMessageSocketDry(unittest.TestCase):
  def test_send(self):
    '''Test queuing and sending of messages.'''
    ms = MockSock(sleep=0.01)
    m = MessageSocket(ms, DECIMAL)
    m.send('one')
    m.send('two')
    #@@FIXME -> te snel op roalds mac
    self.assertEqual(m.sendq.qsize(), 2)
    time.sleep(.1)
    self.assertEqual(m.sendq.qsize(), 0)
    for msg in ['one', 'two']:
      self.assertEqual(ms.sent.get(False, 1), decimal_hdr_encode(msg))

  def test_send_full(self):
    '''Test blocking of messages when queue is full.'''
    m = MessageSocket(MockSock(sleep=1), DECIMAL, send_qsize=2)
    m.send('one')
    m.send('two')
    self.assertRaises(Queue.Full, m.send, 'three')

  def test_receive(self):
    '''Test receiving.'''
    stream = ''.join([decimal_hdr_encode('tock %d' % i) for i in range(10)])
    m = MessageSocket(MockSock(recv=stream, sleep=.01), DECIMAL)
    time.sleep(.1)
    self.assertEqual(m.get(), ['tock %d' % i for i in range(10)])

class TestMessageSocket(unittest.TestCase):
  def par_exec(self, serve_f):
    '''
    Runs a socket server in a separate thread.
    serve_f is a function, with as argument a open socket.
    Returns a socket that is connected to the socket handled by serve_f in a
    separate thread.
    '''
    # create listening socket
    serv_sock = socket.socket()
    serv_sock.bind(('', 0))
    port = serv_sock.getsockname()[1]
    serv_sock.listen(1)

    # fire serving thread, given a server socket
    t = threading.Thread(target=lambda s, f: f(s.accept()[0]), 
      args=(serv_sock, serve_f))
    t.setDaemon(True)
    t.start()

    # connect client socket
    client_sock = socket.socket()
    client_sock.connect(('', port))
    return client_sock

  def test_roundtrip(self):
    '''Test round trip of messages over real sockets.'''
    def echo_serve(s):
      while True:
        msg = s.recv(1024)
        if msg == '':
         break
        s.sendall(msg.upper())
      time.sleep(1)

    m = MessageSocket(self.par_exec(echo_serve), DECIMAL)

    msgs = ['foo', 'bar', 'frob']
    self.assertEqual(m.get(), [])
    for msg in msgs:
      m.send(msg)
    time.sleep(.01)
    self.assertEqual(m.get(), [msg.upper() for msg in msgs])

  def test_not_connected(self):
    '''Try sending to a socket that is not connected.'''
    m = MessageSocket(socket.socket(), DECIMAL)
    time.sleep(.1)
    self.assertRaises(ConnectionError, m.send, 'fail!')

  def test_fail_send(self):
    '''Try sending to a closed socket'''
    def fail_serve(s):
      s.close()

    m = MessageSocket(self.par_exec(fail_serve), DECIMAL, send_qsize=5)
    time.sleep(.1)
    self.assertRaises(ConnectionError, m.send, 'fail')

  def test_fail_recv(self):
    '''Raise exception by early terminating socket'''
    def half_send(s):
      s.sendall(decimal_hdr_encode('fail!'))
      s.close()

    m = MessageSocket(self.par_exec(half_send), DECIMAL)
    time.sleep(.1)
    self.assertRaises(ConnectionError, m.get)

  def test_malformed(self):
    '''Raise exception by receiving malformed data'''
    def serve_crap(s):
      s.sendall('crap' * 100)

    m = MessageSocket(self.par_exec(serve_crap), DECIMAL)
    time.sleep(.1)
    self.assertRaises(Exception, m.get)

  def test_looong(self):
    '''Send long message to stress buffering.'''
    def echo_serve(s):
      while True:
        msg = s.recv(1024)
        if msg == '':
          break
        s.sendall(msg)
    m = MessageSocket(self.par_exec(echo_serve), DECIMAL)
    long = 'l' + 'ooOoo' * 10000 + 'ng'
    self.assertEqual(m.get(), [])
    m.send(long)
    time.sleep(.1)
    self.assertEqual(m.get(), [long])

  def test_slow_recv(self):
    '''Test slow receiving'''
    def slow_echo_serve(s):
      while True:
        msg = s.recv(1024)
        if msg == '':
          break
        for c in msg:
          s.sendall(c)
          time.sleep(.01)

    m = MessageSocket(self.par_exec(slow_echo_serve), DECIMAL)
    self.assertEqual(m.get(), [])
    msg = 'La' + 'la' * 10
    m.send(msg)
    time.sleep(len(msg) * .01 + .1)
    self.assertEqual(m.get(), [msg], 'Sending long message failed.')

class TestClientServer(unittest.TestCase):
  def test_client_server(self):
    s = Server()
    c = Client('', s.port)

    time.sleep(.1)
    sc = s.new_clients()[0]

    sc.send('hi')
    sc.send('there')
    self.assertEqual(c.get_blocking(1), 'hi')
    c.send('hi from client')
    self.assertEqual(c.get_blocking(1), 'there')
    self.assertEqual(c.get(), [])
    self.assertEqual(sc.get_blocking(1), 'hi from client')


if __name__ == '__main__':
  logging.basicConfig(level=logging.ERROR)
  unittest.main()
